package expo.modules.mymodule

import android.content.pm.ApplicationInfo
import android.util.Log
import expo.modules.core.interfaces.Arguments
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.Promise
import expo.modules.kotlin.exception.CodedException
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class MyModule : Module() {
  // Each module class must implement the definition function. The definition consists of components
  // that describes the module's functionality and behavior.
  // See https://docs.expo.dev/modules/module-api for more details about available components.
  override fun definition() = ModuleDefinition {
    // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
    // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
    // The module will be accessible from `requireNativeModule('MyModule')` in JavaScript.
    Name("MyModule")

    // Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
    Constants(
      "PI" to Math.PI
    )

    // Defines event names that the module can send to JavaScript.
    Events("onChange")

    // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
    Function("hello") {
      "Hello world! Love from kotlin 👋"
    }

    // Defines a JavaScript function that always returns a Promise and whose native code
    // is by default dispatched on the different thread than the JavaScript runtime runs on.
    AsyncFunction("setValueAsync") { value: String ->
      // Send an event to JavaScript.
      Log.d("MyModule", "call setValueAsync")
      sendEvent("onChange", mapOf(
        "value" to value
      ))
      value
    }

    data class PackageInfo(val name: String, val packageName: String, val isSystemApp: Boolean)

    AsyncFunction("loadPackagesAsync") { p: Promise ->
      appContext.backgroundCoroutineScope.launch(Dispatchers.Main) {
        Log.d("MyModule", "call loadPackagesAsync")
        try {
          // Simulate async work
          val result = withContext(Dispatchers.IO) {
            val pm = appContext.reactContext!!.packageManager
            val packages = pm.getInstalledPackages(0)
              val maps = packages.map {
                mapOf(
                  "name" to it.applicationInfo!!.loadLabel(pm),
                  "packageName" to it.packageName,
                  "isSystemApp" to (it.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM != 0)
                )
              }
            // Replace with actual async work
            maps
          }
          p.resolve(result)
        } catch (e: Exception) {
          p.reject(CodedException(e))
        }
      }
    }

    AsyncFunction("launchAppByPackageNameAsync") { packageName: String ->
      Log.d("MyModule", "call launchAppByPackageNameAsync")
      val intent = appContext.reactContext!!.packageManager.getLaunchIntentForPackage(packageName)
        ?: throw CodedException("App with package name $packageName cannot be launched")
      appContext.reactContext!!.startActivity(intent)
    }

    // Enables the module to be used as a native view. Definition components that are accepted as part of
    // the view definition: Prop, Events.
    View(MyModuleView::class) {
      // Defines a setter for the `name` prop.
      Prop("name") { view: MyModuleView, prop: String ->
        println(prop)
      }
    }
  }
}
